#ifndef SHADER_S_H
#define SHADER_S_H
#include <glad/glad.h>
#include <windows.h>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <glm/glm.hpp>


class Shader {
public:

    Shader(const char* vertexPath, const char* fragmentPath) {
        
        std::string vertexCode;
        std::string fragmentCode;
        std::ifstream vShaderFile;
        std::ifstream fShaderFile;

        vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);

        try {
            // open files
            vShaderFile.open(vertexPath);
            fShaderFile.open(fragmentPath);

            std::stringstream vShaderStream, fShaderStream;

            //read file's buffer contents into streams
            vShaderStream << vShaderFile.rdbuf();
            fShaderStream << fShaderFile.rdbuf();

            // close file handlers
            vShaderFile.close();
            fShaderFile.close();

            // convert stream into string
            vertexCode = vShaderStream.str();
            fragmentCode = fShaderStream.str();
        } catch (std::ifstream::failure& e) {
            std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl;
        }

        const char* vShaderCode = vertexCode.c_str();
        const char* fShaderCode = fragmentCode.c_str();

        // 2. compie shaders
        // vertex shader
        vertexShader_ = CreateShader(GL_VERTEX_SHADER, &vShaderCode);

        // fragment shaders
        fragmentShader_ = CreateShader(GL_FRAGMENT_SHADER, &fShaderCode);

        // shader program
        shaderProgram_ = CreateProgram();

        DeleteShader();
    }

    void use() {
        glUseProgram(shaderProgram_);
    }

    unsigned int GetProgramId() {
        return shaderProgram_;
    }

    void SetBool(const std::string &name, bool value) const {
        glUniform1i(glGetUniformLocation(shaderProgram_, name.c_str()), (int)value);
    }

    void SetInt(const std::string &name, int value) const {
        glUniform1i(glGetUniformLocation(shaderProgram_, name.c_str()), value);
    }

    void SetFloat(const std::string &name, float value) const {
        glUniform1f(glGetUniformLocation(shaderProgram_, name.c_str()), value);
    }

    void SetVec2(const std::string &name, const glm::vec2 &value) const {
        glUniform2fv(glGetUniformLocation(shaderProgram_, name.c_str()), 1, &value[0]);
    }

    void SetVec2(const std::string &name, float x, float y) const {
        glUniform2f(glGetUniformLocation(shaderProgram_, name.c_str()), x, y);
    }

    void SetVec3(const std::string &name, const glm::vec3 &value) const {
        glUniform3fv(glGetUniformLocation(shaderProgram_, name.c_str()), 1, &value[0]);
    }

    void SetVec3(const std::string &name, float x, float y, float z) const {
        glUniform3f(glGetUniformLocation(shaderProgram_, name.c_str()), x, y, z);
    }

    void SetVec4(const std::string &name, const glm::vec4 &value) const {
        glUniform4fv(glGetUniformLocation(shaderProgram_, name.c_str()), 1, &value[0]);
    }

    void SetVec4(const std::string &name, float x, float y, float z, float w) const {
        glUniform4f(glGetUniformLocation(shaderProgram_, name.c_str()), x, y, z, w);
    }

    void SetMat2(const std::string &name, const glm::mat2 &mat) const {
        glUniformMatrix2fv(glGetUniformLocation(shaderProgram_, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

    void SetMat3(const std::string &name, const glm::mat3 &mat) const {
        glUniformMatrix3fv(glGetUniformLocation(shaderProgram_, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

    void SetMat4(const std::string &name, const glm::mat4 &mat) const {
        glUniformMatrix4fv(glGetUniformLocation(shaderProgram_, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

    unsigned int CreateShader(GLenum type, const GLchar *const* shader_code) {
        unsigned int shader = glCreateShader(type);
        glShaderSource(shader, 1, shader_code, NULL);
        glCompileShader(shader);

        int success;
        char infoLog[512];
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (!success) {
            glGetShaderInfoLog(shader, 512, NULL, infoLog);
            std::cerr << infoLog << std::endl;
        }
        return shader;
    }

    unsigned int CreateProgram() {
        unsigned int sprogram = glCreateProgram();
        glAttachShader(sprogram, vertexShader_);
        glAttachShader(sprogram, fragmentShader_); 
        glLinkProgram(sprogram);
        return sprogram;
    }
    
    void DeleteShader() {
        glDeleteShader(vertexShader_);
        glDeleteShader(fragmentShader_);
    }

private:
    unsigned int shaderProgram_;
    unsigned int vertexShader_;
    unsigned int fragmentShader_;
};


#endif // SHADER_S_H